Skip to content

Docs V2: new information architecture (CIP-3307) - #37

Draft
coderdan wants to merge 123 commits into
mainfrom
v2
Draft

Docs V2: new information architecture (CIP-3307)#37
coderdan wants to merge 123 commits into
mainfrom
v2

Conversation

@coderdan

@coderdan coderdan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Long-lived integration branch for the docs V2 overhaul, tracked in CIP-3307. Opened as a draft — it merges only when the migration completes (CIP-3335); section work stacks as PRs targeting this branch, and this PR's Vercel preview is the staging site for the whole overhaul.

What's in the scaffold (CIP-3325)

  • IA.md — living migration checklist at the repo root: one checkbox per planned page, ticked as sections land, with the branch's working rules (how to move a page, redirect flag, URL conventions)
  • New v2docs collection (content/docs) served from the site root (/docs/get-started/…, /docs/integrations/supabase, …) via a required catch-all route. The legacy tree still serves at /docs/stack/* — both trees coexist until every section migrates, then the legacy tree is deleted
  • Frontmatter facet model: Diátaxis type, components, audience, integration (category / setup / pairsWith), and review-tracking fields (verifiedAgainst, reviewBy)
  • All 8 sections scaffolded with nav + stubs, including /docs/integrations/supabase (the Supabase listing's link target)
  • v2-redirects.mjs — full legacy→v2 map (85/85 pages covered), gated behind ENABLE_V2_REDIRECTS=1 so previews serve both trees during migration; the flag flips on at merge. bun run validate-redirects is wired into prebuild: a legacy page without a mapping fails CI
  • /docs/quickstart vanity redirect (ungated — no legacy traffic on that path)
  • llms.txt / llms-full.txt / sitemap / .mdx raw-markdown mirror all cover the v2 tree (listed first)
  • Removed the AI-citation redirect /reference/eql → /stack/reference/eql — its source collided with (and shadowed) the v2 page at that path

How to review the preview

  • New tree: /docs/get-started, /docs/integrations/supabase, /docs/security/compliance, /docs/reference/eql (stubs for now)
  • Legacy tree unchanged: /docs/stack/quickstart etc.
  • Agent surface: append .mdx to any v2 page URL; /docs/llms.txt

Merge checklist (CIP-3335, end of migration)

  • All IA.md sections ticked; every redirect destination resolves
  • ENABLE_V2_REDIRECTS=1 set for production
  • content/stack, /stack routes, and the legacy loader deleted
  • Final consistency sweep + Supabase listing revision

https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P

coderdan added 2 commits July 2, 2026 17:17
Foundation for the docs overhaul tracked in CIP-3307:

- IA.md: living migration checklist at the repo root (one checkbox per
  planned page; sections tick off as they land on this branch)
- New `v2docs` collection (content/docs) served from the site root via a
  required catch-all route, alongside the legacy tree (content/stack)
  at /stack until every section migrates
- Frontmatter facet model: Diátaxis `type`, `components`, `audience`,
  `integration` (category / setup / pairsWith), and review-tracking
  fields (`verifiedAgainst`, `reviewBy`)
- Section scaffold: get-started, integrations (incl. the
  /integrations/supabase stub the Supabase listing links to), concepts,
  compare, guides, security, solutions, reference — meta.json + stubs
- v2-redirects.mjs: full legacy→v2 map (85/85 pages covered), gated
  behind ENABLE_V2_REDIRECTS=1 so the preview serves both trees during
  migration; /quickstart vanity redirect ships ungated
- scripts/validate-v2-redirects.ts wired into prebuild: CI fails if a
  legacy page has no v2 mapping
- llms.txt, llms-full.txt, sitemap, and the .mdx raw-markdown mirror
  now cover both trees

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
…rence/eql

Two issues caught by smoke-testing the scaffold:
- Stub descriptions containing ":" broke YAML frontmatter parsing
  (500s across the v2 tree) — descriptions are now quoted.
- The AI-citation redirect "/reference/eql" → "/stack/reference/eql"
  shadowed the v2 /reference/eql page (redirects run before the
  filesystem); removed since the v2 page now serves that path.

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
public-docs Ready Ready Preview, Comment Jul 29, 2026 2:26am

Request Review

…t bare root

Two fixes from preview review:
- The bare domain root (Vercel preview URLs) 404'd because the app lives
  under the /docs basePath — added a basePath:false redirect / → /docs.
  In production only /docs/* reaches this app, so previews-only.
- The /docs landing was a standalone (home) page disconnected from the
  v2 nav, with every link pointing at legacy /stack URLs. It's now
  content/docs/index.mdx rendered inside DocsLayout (sidebar + search),
  linking the v2 sections. The catch-all became optional ([[...slug]])
  and the (home) route group is deleted (recoverable from history;
  CIP-3327 refines the landing content).
- The landing's raw-markdown mirror serves at /docs/index.mdx (its URL
  is "/", which can't carry the .mdx suffix).

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
Listing "index" explicitly in meta.json pages forced each section's
index out as a separate child item with the same title as its folder
(clicking "Get started" opened a sub-nav containing another
"Get started"). With index unlisted, Fumadocs merges it into the folder
row: the folder itself links to the page, and children are only real
sub-pages (Integrations → Supabase, not Integrations → Integrations).
The root meta.json keeps "index" — at tree root there is no folder row
to merge into, so the landing needs its own sidebar item.

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
Folders whose only page is their index still rendered as collapsible
sidebar folders with a chevron pointing at nothing. getV2PageTree()
now collapses such folders into plain page items (recursively, so
guides/* and reference/* leaves flatten too); a section becomes a
folder again automatically when its first real sub-page lands.

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
MDX links (markdown and Card hrefs) render through the Link component,
which prefixes the /docs basePath — hardcoded /docs/... links rendered
as /docs/docs/... and 404'd. Convention (enforce via CIP-3349 lint):
internal links in content are always basePath-relative.

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR scaffolds the “Docs V2” information architecture by introducing a new content/docs tree served from the site root (under the existing /docs basePath), while keeping the legacy content/stack tree available at /docs/stack during migration. It also adds a gated legacy→v2 redirect map plus CI validation to ensure legacy pages won’t become orphaned when redirects are enabled.

Changes:

  • Add a new v2docs collection + v2source loader, plus root catch-all routes/layout for the v2 docs tree.
  • Introduce a full legacy→v2 redirect map (v2-redirects.mjs) gated by ENABLE_V2_REDIRECTS=1, with a prebuild validation script to enforce coverage.
  • Scaffold v2 section stubs/meta and update sitemap + LLM surfaces (llms.txt, llms-full.txt, .mdx raw mirror) to include the v2 tree first.

Reviewed changes

Copilot reviewed 65 out of 65 changed files in this pull request and generated 25 comments.

Show a summary per file
File Description
v2-redirects.mjs Adds the full legacy /stack/* → v2 redirect mapping list (flag-gated in Next config).
next.config.mjs Wires in gated v2 redirects, adds preview root redirect, and adds .mdx rewrite for v2 raw mirror; removes old /reference/eql redirect collision.
scripts/validate-v2-redirects.ts Adds CI/prebuild gate to ensure every legacy content/stack page is covered by v2 redirects.
package.json Adds validate-redirects script and runs it during prebuild.
source.config.ts Defines the new v2docs collection and its frontmatter facet schema.
src/lib/source.ts Adds v2source, v2 page tree shaping (flattenEmptyFolders), and broadens getLLMText typing for both trees.
src/app/[[...slug]]/layout.tsx Adds the v2 docs layout (DocsLayout + v2 tree) at the root catch-all segment.
src/app/[[...slug]]/page.tsx Adds the v2 docs page renderer, metadata generation, and markdown mirror URL generation.
src/app/llms.mdx/v2/[[...slug]]/route.ts Adds the v2 raw-markdown mirror route for agent/LLM consumption.
src/app/llms.txt/route.ts Lists v2 pages first, then legacy pages, in llms.txt.
src/app/llms-full.txt/route.ts Emits the concatenated markdown for v2 pages first, then legacy pages.
src/app/sitemap.ts Includes both v2 and legacy pages in sitemap (v2 first).
src/app/og/docs/[...slug]/route.tsx Import ordering tweak only.
src/app/api/search/route.ts Import ordering tweak only.
src/app/layout.tsx Import ordering tweak only.
src/app/stack/layout.tsx Import ordering tweak only.
src/app/stack/[[...slug]]/page.tsx Import ordering tweak only.
src/proxy.ts Import ordering tweak only.
src/lib/posthog/provider.tsx Import ordering tweak only.
src/components/icons/supabase.tsx Fixes missing semicolon in return statement.
src/app/(home)/page.tsx Removes the standalone legacy docs landing page implementation.
src/app/(home)/layout.tsx Removes the legacy home layout wrapper.
IA.md Adds the migration checklist + branch workflow rules for the v2 overhaul.
content/docs/meta.json Adds v2 root meta defining top-level section ordering.
content/docs/index.mdx Adds the v2 docs landing page content (Cards + LLM surface links).
content/docs/get-started/meta.json Adds v2 “Get started” section meta.
content/docs/get-started/index.mdx Adds v2 “Get started” stub page.
content/docs/integrations/meta.json Adds v2 “Integrations” section meta.
content/docs/integrations/index.mdx Adds v2 “Integrations” stub page.
content/docs/integrations/supabase/meta.json Adds v2 Supabase integration meta (custom icon).
content/docs/integrations/supabase/index.mdx Adds v2 Supabase stub page with facet example.
content/docs/concepts/meta.json Adds v2 “Concepts” section meta.
content/docs/concepts/index.mdx Adds v2 “Concepts” stub page.
content/docs/compare/meta.json Adds v2 “Comparisons” section meta.
content/docs/compare/index.mdx Adds v2 “Comparisons” stub page.
content/docs/guides/meta.json Adds v2 “Guides” section meta.
content/docs/guides/index.mdx Adds v2 “Guides” stub page.
content/docs/guides/development/meta.json Adds v2 “Development” guides meta.
content/docs/guides/development/index.mdx Adds v2 “Development” stub page.
content/docs/guides/migration/meta.json Adds v2 “Data migration” guides meta.
content/docs/guides/migration/index.mdx Adds v2 “Data migration” stub page.
content/docs/guides/deployment/meta.json Adds v2 “Deployment” guides meta.
content/docs/guides/deployment/index.mdx Adds v2 “Deployment” stub page.
content/docs/guides/troubleshooting/meta.json Adds v2 “Troubleshooting” guides meta.
content/docs/guides/troubleshooting/index.mdx Adds v2 “Troubleshooting” stub page.
content/docs/security/meta.json Adds v2 “Architecture & security” section meta.
content/docs/security/index.mdx Adds v2 “Architecture & security” stub page.
content/docs/security/compliance/meta.json Adds v2 “Compliance” meta under security.
content/docs/security/compliance/index.mdx Adds v2 “Compliance” stub page.
content/docs/solutions/meta.json Adds v2 “Solutions” section meta.
content/docs/solutions/index.mdx Adds v2 “Solutions” stub page.
content/docs/reference/meta.json Adds v2 “Reference” section meta.
content/docs/reference/index.mdx Adds v2 “Reference” stub page.
content/docs/reference/eql/meta.json Adds v2 “EQL” reference meta.
content/docs/reference/eql/index.mdx Adds v2 “EQL” stub page.
content/docs/reference/stack/meta.json Adds v2 “Stack SDK” reference meta.
content/docs/reference/stack/index.mdx Adds v2 “Stack SDK” stub page.
content/docs/reference/proxy/meta.json Adds v2 “Proxy” reference meta.
content/docs/reference/proxy/index.mdx Adds v2 “Proxy” stub page.
content/docs/reference/cli/meta.json Adds v2 “CLI” reference meta.
content/docs/reference/cli/index.mdx Adds v2 “CLI” stub page.
content/docs/reference/auth/meta.json Adds v2 “Auth” reference meta.
content/docs/reference/auth/index.mdx Adds v2 “Auth” stub page.
content/docs/reference/workspace/meta.json Adds v2 “Workspace & account” reference meta.
content/docs/reference/workspace/index.mdx Adds v2 “Workspace & account” stub page.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread content/docs/index.mdx Outdated
Comment thread content/docs/index.mdx Outdated
Comment on lines +26 to +30
<Card title="Integrations" href="/docs/integrations" description="Platforms, ORMs, frameworks, auth providers, and runtimes." />
<Card title="Concepts" href="/docs/concepts" description="How searchable encryption, key management, and identity-aware encryption work." />
<Card title="Guides" href="/docs/guides" description="Development workflow, data migration, deployment, and troubleshooting." />
<Card title="Architecture & security" href="/docs/security" description="Trust model, components, availability, audit, and compliance — for security review." />
<Card title="Solutions" href="/docs/solutions" description="PII protection, HIPAA, AI/RAG, data residency, and provable access." />
Comment thread content/docs/index.mdx Outdated
<Card title="Guides" href="/docs/guides" description="Development workflow, data migration, deployment, and troubleshooting." />
<Card title="Architecture & security" href="/docs/security" description="Trust model, components, availability, audit, and compliance — for security review." />
<Card title="Solutions" href="/docs/solutions" description="PII protection, HIPAA, AI/RAG, data residency, and provable access." />
<Card title="Reference" href="/docs/reference" description="EQL, the Stack SDK, Auth, the CLI, and Proxy — precise API documentation." />
Comment thread content/docs/index.mdx Outdated
Comment thread IA.md Outdated
Comment on lines +20 to +21
- **Moving a page = ** move the file into `content/docs`, update its facets,
fix inbound links, confirm its `v2-redirects.mjs` entry, tick it here.
Comment thread content/docs/guides/index.mdx Outdated
Comment thread content/docs/guides/development/index.mdx Outdated
Comment thread content/docs/guides/deployment/index.mdx Outdated
Comment thread content/docs/guides/migration/index.mdx Outdated
Comment thread content/docs/guides/troubleshooting/index.mdx Outdated
Seven pages replacing the v2-era EQL reference, written against the
eql_v3 branch of cipherstash/encrypt-query-language (3.0.0):

- index: what EQL is, the v3 domain-variant model, install (single SQL
  script, idempotent), dbdev, Docker, migration/runtime permission
  split, managed-Postgres rationale
- types: 10 scalar families × variants matrix; bool storage-only;
  _ord/_ord_ore twins; index terms per variant
- operators: per-variant support matrix, typed-operand rule, no-LIKE,
  fail-loud blockers, query shapes, function-form equivalents
- indexes: functional indexes on term extractors, engagement
  requirements, sort-key form for index-streamed ORDER BY, EXPLAIN
  checklist, large-table build guidance
- json: ste_vec model, per-node-type terms (hm XOR oc), containment +
  GIN, field access, path queries, blocked native jsonb operators
- functions: comparisons, extractors, min/max only (no SUM/AVG),
  version()
- payload-format: v/i/c envelope (wire version still v:2), hm/ob/bf
  term keys, sv document shape, annotated examples (absorbs the legacy
  CipherCell page)

Cross-page consistency verified against the shipped SQL: equality on
_ord variants compares ORE terms (no hm in _ord payloads), and bare
ORDER BY is correct but extractor-form sort keys stream from the index.

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
Comment thread v2-redirects.mjs Outdated
{
source: "/stack/quickstart",
destination: "/get-started/quickstart",
permanent: true,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't make these permanent for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d0ccd7e — all 71 entries flipped to permanent: false, with a header note to revisit as part of CIP-3335 once the map has soaked post-merge.

coderdan added 7 commits July 2, 2026 19:56
EQL is an abstraction over SQL the way Tailwind is over CSS — the docs
now follow the same shape: Install → Core concepts → type categories →
Indexes → query patterns, increasing in complexity. Each type-category
page is the complete reference for its types (variants, payload shape,
operators/functions, example queries on one page).

- index: trimmed to the Install page
- core-concepts (new): the canonical home for shared mechanics —
  variant model, payload anatomy (v/i/c envelope + hm/ob/bf terms,
  absorbs payload-format/CipherCell), typed-operand rule, fail-loud
  blockers, ORE-equality on _ord, term-leakage pointer
- numbers-and-dates, text, booleans (new) + json (reworked): category
  pages; text owns the no-LIKE treatment; json absorbs the sv payload
  shape; booleans framed as "every type has a storage-only variant —
  for bool it's the only one"
- filtering, sorting, grouping-and-aggregates, joins (new): cross-type
  query patterns; joins headlines the same-keyset constraint
- deleted: types.mdx, operators.mdx, functions.mdx, payload-format.mdx
  (content redistributed; URLs never shipped publicly, no redirect debt)
- Anti-drift rule recorded in IA.md: mechanics live ONLY in
  core-concepts; category/query pages link, never restate
- meta.json: flat URLs with ---Types---/---Indexes---/---Queries---
  sidebar separators; legacy redirect map retargeted (queries →
  filtering, cipher-cell → core-concepts)

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
…load v:3

Review feedback on the EQL section:

- Variant tables: generic form first, then full enumeration of every
  concrete domain name (Tailwind-style); capability column made
  concise; "index term carried" column dropped — term internals live
  in core-concepts' payload anatomy
- SEM specifiers documented as a concept in core-concepts: a trailing
  mechanism suffix (_ord_ore) pins WHICH searchable-encryption
  mechanism implements a capability; _ord tracks the default
  (currently ORE). Replaces the "twins" framing. Each orderable type
  page lists its specifiers under an "SEM specifiers" heading, noting
  the OPE specifier arriving for all orderable types (incl. text) in
  the v3 release
- Payload `v` field documented as the EQL version (3) per team
  decision 2026-07-02; all payload examples updated from v:2

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
…perators and Functions

Review feedback:
- Dates & times split out of Numbers — same traits, distinct semantics;
  each page's examples now match its domain (payroll vs audit-event
  time windows / retention cutoffs / newest-first)
- CREATE TABLE examples get an explicit "Example" sub-heading + lead-in
- Operators and Functions are separate sections on every type page —
  operators as the per-variant support matrix, functions as the
  form-equivalents table (+ MIN/MAX, which only exist as functions)
- IA.md: split reflected; query-performance follow-up added (CIP-3351 —
  the v3 branch already folded the v2 perf guide into
  database-indexes.md, which our indexes page absorbed)

Claude-Session: https://claude.ai/code/session_01ACPpFPHvKtrV48nbEYuv7P
- indexes.mdx: cast query-shape example params to their EQL domain
  types, consistent with the typed-operand rule
- numbers/dates-and-times/text: the fail-loud note now scopes to
  operators — ORDER BY on a variant without an ordering term doesn't
  raise, it silently returns a meaningless order (links Sorting)
- v2-redirects.mjs: all entries permanent: false while the IA settles
  (per review); flip to permanent post-merge soak (CIP-3335)
- IA.md: fix unmatched bold around 'Moving a page'
- add placeholder pages for /concepts/searchable-encryption (CIP-3333)
  and /guides/troubleshooting/query-performance (CIP-3351) so the EQL
  reference's forward links resolve instead of 404ing
@coderdan

coderdan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Review comments addressed in d0ccd7e:

  • /docs/-prefixed links (Copilot, ~20 comments): already fixed in 8514932 (fix(v2): make all MDX links basePath-relative) — those comments predate it. CIP-3349 adds the lint to prevent regressions.
  • Redirects: all entries now permanent: false; flip tracked under CIP-3335.
  • IA.md: unmatched-bold fix.
  • Added placeholder pages for /concepts/searchable-encryption (CIP-3333) and /guides/troubleshooting/query-performance (CIP-3351) so forward links from the EQL reference resolve.

Docs V2: EQL v3 reference section, Tailwind-shaped (CIP-3326)
docs: add an EQL pin resolver so patch bumps stop relying on memory
…aying `any`

The generated Stack API reference documents 13 types as `any` and gives
AuthStrategy the wrong description entirely. Both come from one missing
compiler option.

@cipherstash/protect-ffi's `exports` map routes the `node` condition to
lib/index.d.cts (the real type surface) and everything else to
`default: ./dist/wasm/protect_ffi.js`, which ships no `types`. TypeScript
therefore falls through to the sibling dist/wasm/protect_ffi.d.ts — raw
wasm-bindgen output — where none of the hand-written types exist.

`moduleResolution: "bundler"` does not imply the `node` condition;
packages/stack/tsconfig.json adds `customConditions: ["node"]`, which is why
the stack team never sees this. But typedoc.tsconfig.json extends stack's ROOT
tsconfig, which does not set it. Set it on the generated config.

Confirmed with a minimal repro against protect-ffi 0.30.0 (the version stack
1.0.0-rc.4 pins exactly): `moduleResolution: bundler` alone yields
"has no exported member 'ProtectError'. Did you mean 'encryptQuery'?"; adding
customConditions typechecks clean.

Also turns error checking back on. It was disabled to tolerate this, on the
theory that the references were "unresolved even though the source is correct"
and TypeDoc would still emit accurate signatures. It does not — an unresolved
import becomes `any` in the output, so the workaround converted a loud failure
into a silently wrong reference. With the condition fixed the surface
typechecks with 0 errors, so the next resolution break fails the build instead.

Generating with and without the fix, back to back against the same stack tag,
changes 23 pages. Representative:

  EncryptionError.code            any -> ProtectErrorCode
  EncryptQueryOperation.execute() Promise<Result<any, ...>>
                                    -> Promise<Result<EncryptedQueryResult, ...>>
  encryptQuery plaintext param    any -> Plaintext | null | undefined
  AuthStrategy description        the module's blurb -> the type's own docs

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
docs: resolve protect-ffi's real types so the Stack reference stops saying `any`
coderdan added 3 commits July 28, 2026 17:31
EQL 3.0.4 ships the manifest-extraction fixes from
cipherstash/encrypt-query-language#427, so `eql_v3.grouped_value` and
`public.eql_v3_json` now resolve from the catalog and no longer need drift-check
exemptions. Verified against the released manifest: grouped_value(jsonb) and
lints() present, 53 domains (was 52) including public.eql_v3_json, and the
`match` capability restored to text_match and both text_search variants.

Only `eql_v3.query_text_eq` stays on the allowlist — the 40 scalar
query-operand domains created in a `DO ... EXECUTE` block are a separate gap
that 3.0.4 does not address. Removing the other two means a regression in
either now fails the drift check instead of staying quiet.

Also always stamp the pinned tag as the manifest's version rather than only
substituting when it reads "DEV". EQL's release workflow derives that field
from a tag input that can arrive empty, in which case json.sh falls back to
"DEV" — which 3.0.4 shipped, and which would otherwise render as "EQL DEV" in
the banner on every reference page. The existing guard already covered that
case; this extends it to warn when a manifest is stamped with a DIFFERENT
release, which would be a packaging mix-up worth seeing rather than a cosmetic
default worth silencing.

bun run build passes: drift check clean against 3.0.4, 1990 functions,
banner reads EQL 3.0.4.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
Brings main's four commits onto v2 so #37 (v2 -> main) is mergeable again.
Everything main has that v2 lacks is a README URL fix and a Next bump; there is
no content divergence.

One conflict, in package.json: main bumped next 16.2.3 -> 16.2.6 (dependabot
#18) while v2 added mermaid to the same dependency block. Kept both — v2's
mermaid and main's newer next.

Also regenerates bun.lock, which main's bump never touched: its package.json
asked for next 16.2.6 while its lockfile still pinned 16.2.3, so
`bun install --frozen-lockfile` failed on main. It passes here.

`bun run build` is green end to end on the merge: EQL drift check clean,
mermaid diagrams parse, all 188 legacy redirects covered, 706 pages compiled.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
docs: EQL 3.0.4 — grouped_value, @@ fuzzy match, and the encrypted-JSON surface
coderdan added 2 commits July 28, 2026 19:06
… API gate

Three changes, all about the docs telling the truth about the SDK.

1. Add @cipherstash/stack-supabase to the TypeDoc entry points.

Stack 1.0 split the Supabase adapter into its own package, and the entry points
were never updated, so NOTHING in it reached the generated reference —
encryptedSupabaseV3, the query builder, and the EQL-version constraints on its
encrypted operators were all absent. 19 pages now generate (763 total, up from
706), including the constraint that free-text and JSON operators are
"unavailable with EQL 3.0.2+" because PostgREST cannot express the required
eql_v3.query_* cast. That is now published automatically instead of relying on
someone reading the source.

2. Resolve a workspace package's subpath imports to source.

stack-supabase imports its sibling by package name (@cipherstash/stack,
/schema, /adapter-kit, ...), which resolves through that package's exports map
to ./dist — and the clone is never built, so all 42 imports were TS2307 and
every cross-package type would have documented as `any`. Derive the tsconfig
`paths` from the sibling's own exports map, rewriting ./dist/x.js to
./packages/<pkg>/src/x.ts, rather than hardcoding twelve subpaths that would
rot the next time Stack adds one. Two of them are not guessable
(`/types` -> types-public.ts, `/v3` -> encryption/v3.ts), which is the argument
for deriving rather than transcribing.

3. Say what the content API gate actually checks.

It printed "no deprecated or non-existent API found in documentation" while the
Supabase page taught `.contains()` for encrypted free-text, which raises. It is
a denylist of 7 patterns, not verification, so it now reports the rule count and
files scanned and states the limitation outright.

Deliberately NOT adding the version-diff denylist discussed: `contains()` was
repurposed rather than removed, so it is present in every tag and no
symbol-presence or signature diff fires on it. Catching that class needs the
examples typechecked against the SDK; the header now records that.

Also removes CLAUDE.md's "Required Skills" section. All six skills it mandated
(encryption, secrets, drizzle, supabase, dynamodb, update-docs) do not exist,
and its validation checklist was stale too — `switcher` appears in 0 files and
the "Good to know" callout in 1, versus <Callout> in 37. Its accurate items are
already covered by the Frontmatter, Code Blocks, and Formatting sections above.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
docs: generate the stack-supabase reference, and stop overstating the API gate
coderdan added 2 commits July 29, 2026 11:03
`validate-links` only ever looked at `content/stack`, and only at markdown
link syntax. That left the entire v2 tree unchecked, plus every
`<Card href=…>` in both trees — so a v2 page could be renamed or merged
away and every link to it would 404 against a green build. It also never
checked `#fragments` at all, in either tree.

It now walks both collections with their real base URLs (content/docs at
the root, content/stack under /stack), reads JSX href attributes as well
as markdown links, and resolves each `#fragment` against the target
page's headings.

Getting anchors right needed care:

- Headings are slugged with `github-slugger`, the same one Fumadocs uses,
  one instance per page so repeated headings get the same -1/-2 suffixes
  the renderer emits. Hand-rolled slugification is wrong in ways that
  matter here — "Sorting & range" is `sorting--range`, not
  `sorting-range`.
- Fumadocs' explicit `## Heading [#custom-id]` syntax is honoured.
- `<include cwd>` partials are inlined first, since their headings become
  anchors on the including page.
- Fenced code blocks are blanked (line numbers preserved), so a `#` in a
  bash sample isn't mistaken for a heading.

Non-page targets are resolved rather than skipped: static files under
public/ and static route handlers under src/app. Previously anything not
starting with /stack/ was waved through, which is precisely how the v2
tree escaped checking.

This surfaced 14 genuine breakages, all fixed here:

- 13 links to `searchable-encryption#exact-match`, `#match-pattern` and
  `#range--order` across 6 files. Those headings were renamed at some
  point to "Exact matching", "Free-text search" and "Sorting and range
  queries"; the links have been landing at the top of the page ever
  since, silently.
- `reference/drizzle.mdx` pointed at `/integrations/drizzle`, which does
  not exist on v2 yet — it arrives with #39. Dropped the forward
  reference; the API list under it already covers the surface.

TypeDoc output is gitignored and produced by `generate-docs`, so run
standalone on a fresh checkout every link into it looks broken. The
script now says so instead of emitting a wall of spurious errors —
`prebuild` generates before validating, which is why CI never sees it.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
Adds a `Docs / Internal links` job so a broken link is a named, blocking
status check rather than a failure buried in a Vercel deploy log.

Worth being precise about what this does and does not add: `validate-links`
already runs inside `prebuild`, and bun runs `prebuild` before `build`, so
broken links have always failed the Vercel build. What was missing is a
check that branch protection can require — which is the point of this job,
not new coverage.

It runs `generate-docs` first. The TypeDoc reference under
content/stack/reference/stack/latest/ is gitignored and built from a clone
of cipherstash/stack; roughly 18 links point into it, so skipping that step
would report them all as missing. All three repos involved are public, so
no secrets are needed. On a cold clone the whole job is install 4s +
generate 16s + validate 1s.

Scoped to `v2` only. `main` trails it by ~100 commits and its bun.lock is
out of sync with its package.json, so `bun install --frozen-lockfile`
fails there for reasons unrelated to links — verified, not assumed. Add
`main` to the trigger once #37 lands and makes it current.

Verified end to end against a pristine clone: the steps as written pass
(exit 0), and a deliberately broken link fails the job (exit 1).

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
coderdan added 3 commits July 29, 2026 11:41
Fumadocs ships the inverse of what we want: prose body text is
`--color-fd-foreground` at 90%, while DocsDescription — the standfirst
directly under the H1 — renders `text-fd-muted-foreground`. So the lead
line was the quietest text on the page and the body the loudest.

Swaps the two:

- `.prose` sets `--tw-prose-body` to `--color-fd-muted-foreground`. Only
  that one variable moves; headings, links, bold, code, quotes and
  captions each have their own, so they keep full contrast and now read
  as more distinct against the softer paragraph text.
- Both page routes pass `text-fd-foreground` to DocsDescription.

Both sides reference theme tokens, so light and dark follow the same rule
with no per-mode override.

The `.prose` rule is deliberately **unlayered**. Fumadocs declares
`.prose` in `@layer utilities`, and layer order beats both specificity and
source order — the same declaration inside the `@layer components` block
further down this file compiles fine and is silently ignored. Verified
against the built CSS rather than assumed: the shipped rule lands
unlayered, after fumadocs' own, and unlayered declarations outrank every
layer without needing `!important`.

Also verified in the built HTML that tailwind-merge resolves the class
conflict rather than emitting both — the description renders as
`text-lg mb-0 text-fd-foreground`, with the default muted class dropped.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
All four of Copilot's findings were real. Each was reproduced against the
corpus before fixing, and re-run after.

1. Includes renumbered the rest of the file. `inlineIncludes()` spliced a
   partial's lines into the including page, shifting every position below
   the directive by the partial's length. A broken link on line 110 of
   reference/eql/text.mdx was reported as line 193 — 83 lines out. The
   comment claiming positions above the directive were unaffected was
   right but useless; the damage was all below it. Lines now carry their
   own origin (`SourceLine { text, file, line }`), so a link inside a
   partial is reported against the partial's own path and line rather
   than an offset position in whichever page pulled it in.

2. Relative links escaping a collection were skipped silently.
   `resolveRelative()` returned null and the caller `continue`d, so
   `[x](../../../etc/passwd)` produced no output at all. Escapes are now
   reported, as are relative links inside shared partials — those have no
   single base to resolve against, so they need an absolute path.

3. `resolved.startsWith(base)` matched sibling directories with a shared
   prefix: content/docs-other satisfied the content/docs guard. Replaced
   with an `isInside()` helper requiring an exact match or a path
   separator. Impact was diagnostic rather than missed errors — the link
   still failed, but as "No such page" instead of "resolves outside
   content/docs".

4. Trailing slashes defeated the shape checks. `/a/index/` and `/a.mdx/`
   fell through to the generic "no such page" instead of their precise
   diagnosis, and a bare `/docs` missed the basePath check entirely.
   Normalising the trailing slash before the checks fixes all three.

Also de-duplicates reports, since a partial is scanned once per including
page. Defensive today — each partial has exactly one includer.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
fix(ci): validate links across both content trees, and check anchors
coderdan added 5 commits July 29, 2026 12:03
Two changes to Callout, plus a correction.

**The correction.** I claimed in #82's description that muting `.prose`
body text also muted callouts. That was wrong: Fumadocs hard-codes
`text-fd-muted-foreground` on `CalloutDescription`, set directly on that
div, so it overrides any inherited prose colour — and only `.prose`
itself consumes `--tw-prose-body`. Callout text has always been muted;
the body change did not touch it. The fix is the same either way, but
the reasoning in that description was not.

**Body text** now uses `text-fd-foreground`, matching the title (the
container is `text-fd-card-foreground`, which resolves to the same value
in both themes). A callout reads as one block instead of a heading over
greyed-out copy, and stands out from the surrounding muted body copy,
which is the point of a callout.

**Padding** doubles, `p-3 ps-1` → `p-6 ps-2`. `ps` stays proportionally
small because the accent bar is the container's first child and sits
near the edge.

Both need reaching the description, which Fumadocs' `Callout` does not
expose, so we compose `CalloutContainer` / `CalloutTitle` /
`CalloutDescription` — its exported primitives — instead. The container
still resolves the `warn`/`tip` aliases and picks the icon, so only the
trivial wrapper is reimplemented.

The wrapper also emits `data-callout`, which Fumadocs does not. global.css
has had a `[data-callout]` rule since the theme work that consequently
matched nothing; it now applies (its `border-radius: 2px` was already
coming from the `[class*="rounded-xl"]` rule above it, so nothing moves).

Verified in the built HTML rather than assumed — tailwind-merge collapses
both conflicts, leaving `p-6 ps-2` on the container and
`prose-no-margin empty:hidden text-fd-foreground` on the description, with
no muted class left anywhere in the callout subtree.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
Reverts the `text-fd-foreground` from 308f082. `text-fd-muted-foreground`
— Fumadocs' default for CalloutDescription — is the same token the
sidebar's resting nav items use, so callouts sit at the weight of the
surrounding chrome instead of shouting.

That means the colour lands back where it started, and the net effect of
the callout work is the doubled padding plus the `data-callout` hook.

Dropping the colour override also drops the reason for composing
CalloutContainer / CalloutTitle / CalloutDescription by hand: reaching the
description was the only thing Fumadocs' `Callout` wrapper could not do.
It spreads unknown props onto the container, so the wrapper is now four
lines over the public component, with no copy of its alias resolution,
icon selection or title rendering to drift out of step.

Verified in the built HTML that the callout body and a resting sidebar
item carry the same class, and that the padding survived the
simplification:

  container: … bg-fd-card text-sm text-fd-card-foreground shadow-md p-6 ps-2
  body:      text-fd-muted-foreground prose-no-margin empty:hidden
  sidebar:   … rounded-lg p-2 text-start text-fd-muted-foreground …

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
Third setting for callout text, and the one between the two already tried:
the colour prose body copy had before this branch muted it,
`color-mix(in oklab, var(--color-fd-foreground) 90%, transparent)`.

`text-fd-foreground` read as shouting; `text-fd-muted-foreground` matched
the surrounding paragraphs exactly, leaving the callout to carry its
distinction entirely on card background, border, accent bar and icon.
This keeps it a step above the page copy without competing with headings.

Done in CSS rather than by threading a class through the component,
because Fumadocs puts `text-fd-muted-foreground` directly on the
description div — inheritance from the container cannot reach it, so the
element has to be selected either way. Doing it here keeps the wrapper in
mdx-components.tsx at four lines over the public `Callout`, with nothing
of its internals duplicated.

The selector leans on `data-callout` (ours, from that wrapper) and
`prose-no-margin` (Fumadocs' marker on the description's prose block).
Unlayered, for the same reason as the `.prose` rule above it: the utility
class being overridden lives in `@layer utilities`, and layer order beats
specificity.

Verified in the built CSS that the rule ships unlayered with both the srgb
fallback and the themed `color-mix`, and in the built HTML that the
selector matches the rendered description and the doubled padding
survived.

Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
style: mute body copy, and let the standfirst lead
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants